﻿using System;
using System.Collections.Generic;
using System.Text;
using System.Drawing;
using Silvermoon.Controls.Classes;
using Silvermoon.Animations;
using Silvermoon.Core;
using Silvermoon.Controls.Interfaces;
using Silvermoon.Controls.Sys;
using Silvermoon.Controls.Base;

namespace Silvermoon.Controls
{

    /// <summary>
    /// Layouts controls against a <see cref="T:GridDefinition"/> and enables animated transitions to another GridDefinition.
    /// </summary>
    /// <remarks>
    /// Why are GridDefinition and Cells separated from Grid?
    /// -> because this enables a static definition that can be reused for instances of grid without the need to duplicate each GridDefinition/Cell.
    /// 
    /// Why is a control not directly associated with a cell, e.g. with Cell.Control?
    /// -> because GridDefinitions and Cells are designed to be static and reusable. Binding the Control directly to a cell breaks the separation
    /// and binds the cell to an instance of Grid.
    /// </remarks>
    public class AnimationGrid : CompositeControlBase
    {
        #region fields
        private int transitionLevel;
        private int cacheIndex;
        private List<GridContainer> containerCache;
        private GridDefinition definition;
        private GridDefinition state;
        private GridDefinition transitionState;
        private GridOffsets stateOffsets;
        protected GridOffsets cellOffsets;
        protected GridOffsets targetOffsets;
        private IntAnimation animation;
        private int level = MaxLevel;


        /// <summary>
        /// Specifies the maximum value for Level
        /// </summary>
        public const int MaxLevel = 8192;

        #endregion
        #region ctor

        public AnimationGrid()
            : base()
        {
            Controls = new Dictionary<int, Control>();
        }


        protected override void Dispose(bool dispose)
        {
            //foreach (var c in Controls.Values)
            //{
            //    c.Dispose();
            //}
            Controls.Clear();
            if (containerCache != null)
            {
                foreach (GridContainer c in containerCache) c.Dispose();
                containerCache.Clear();

            }
            base.Dispose(dispose);
        }

        #endregion
        #region props

        /// <summary>
        /// The Level the specifies the interpolation between State and TransitionState, where 0 means the appearance refers to the State, and
        /// Level==EndLevel refers to the TransitionState. Values between are interpolated.
        /// </summary>
        protected int TransitionLevel
        {
            get { return transitionLevel; }
            set
            {
                if (transitionLevel != value)
                {
                    transitionLevel = value;
                    Invalidate(InvalidationFlag.Layout);
                }
            }
        }

        /// <summary>
        /// The Level the specifies the interpolation between the Definition and the State, where 0 means the appearance refers to the definition, and
        /// Level==EndLevel refers to State. Values between are interpolated.
        /// </summary>
        public int Level
        {
            get { return level; }
            set
            {
                if (level != value)
                {
                    level = value;
                    Invalidate(InvalidationFlag.Layout);
                }
            }
        }

        /// <summary>
        /// Gets or sets the definition of rows, columns and cells that are refered to define a state.
        /// </summary>
        public GridDefinition Definition
        {
            get { if (definition == null) definition = new GridDefinition(); return definition; }
            set { definition = value; Invalidate(InvalidationFlag.Layout); }
        }

        /// <summary>
        /// Gets or sets the state for the grid. If the grid is in a transition, the value returned is the state to be transitioning to.
        /// If set, the state is changed immediately to the final level.
        /// See also <seealso cref="ChangeState"/>.
        /// </summary>
        public GridDefinition GridState
        {
            get { return transitionState != null ? transitionState : state; }
            set
            {
                if (value != state)
                {
                    AbortAnimation();
                    state = value;
                    Level = MaxLevel;
                    transitionState = null;
                    transitionLevel = 0;
                    Invalidate(InvalidationFlag.Layout | InvalidationFlag.Size);
                }
            }
        }

        /// <summary>
        /// Gets or sets the state to which the grid is transitioning to.
        /// </summary>
        protected GridDefinition TransitionState
        {
            get { return transitionState; }
            set
            {
                if (value != transitionState)
                {
                    transitionState = value;
                    Invalidate(InvalidationFlag.Layout | InvalidationFlag.Size);
                }
            }
        }

        /// <summary>
        /// Gets the controls dictionary that binds a cell with a control.
        /// Note thate the specified key is the index of the Cell to be associated with.
        /// </summary>
        public Dictionary<int, Control> Controls { get; private set; }

        public AnimationGridUnitCollection Rows { get { return Definition.Rows; } }
        public AnimationGridUnitCollection Columns { get { return Definition.Columns; } }
        public IList<GridCell> Cells { get { return Definition.Cells; } }

        /// <summary>
        /// Gets whether the grid is in a transition.
        /// </summary>
        public bool IsInTransition { get { return TransitionLevel != 0; } }


        #endregion
        #region methods

        public void AddControls(params Control[] controls)
        {
            int idx = Controls.Count;
            foreach (Control c in controls)
            {
                Controls[idx++] = c;
            }
        }

        private void AbortAnimation()
        {
            if (animation != null) animation.Abort();
        }

        /// <summary>
        /// Changes the state of this grid to another <see cref="T:GridDefinition"/> in a transitio.
        /// </summary>
        /// <param name="state">The State to which to transitioning.</param>
        /// <param name="duration">The duration for the transition to the new state to take.</param>
        /// <param name="startDelay">Specifies how long to delay the transition to the new state.</param>
        public void ChangeState(GridDefinition state, int duration, int startDelay)
        {
            if (GridState == state) return;

            if (animation != null)
            {
                animation.Abort();
                if (!animation.IsCompleted)
                {
                    if (transitionState == this.definition)
                    {
                        Level = MaxLevel - transitionLevel;
                    }
                    else
                    {
                        Level = transitionLevel;
                        GridState = TransitionState;
                    }
                }
            }

            animation = new IntAnimation { Duration = duration, To = AnimationGrid.MaxLevel };
            animation.Progress += new EventHandler<AnimEventArgs<int>>(OnProgress);
            animation.Completed += new EventHandler<AnimEventArgs<int>>(OnComplete);

            TransitionState = state;
            PreLoad();
            animation.InvokeAsync();
        }

        protected virtual void OnComplete(object sender, AnimEventArgs<int> e)
        {
            TransitionLevel = MaxLevel;
            Invalidate(InvalidationFlag.Appearance);
            //Screen.Progess();
            GridState = TransitionState;
            //            Level = TransitionLevel;
            TransitionLevel = 0;
        }

        protected virtual void OnProgress(object sender, AnimEventArgs<int> e)
        {
            TransitionLevel = e.Value;
        }

        /// <summary>
        /// Changes the state of this grid to another <see cref="T:GridDefinition"/> in a transition.
        /// See also <seealso cref="State"/>.
        /// </summary>
        /// <param name="state">The State to which to transitioning.</param>
        /// <param name="duration">The duration for the transition to the new state to take.</param>
        public void ChangeState(GridDefinition definition, int duration)
        {
            ChangeState(definition, duration, 0);
        }

        protected override void OnLayout()
        {
            if (CheckFlag(InvalidationFlag.Size | InvalidationFlag.Layout | InvalidationFlag.Margin))
            {
                LayoutGrid();
            }
            base.OnLayout();
        }

        public void LayoutGrid()
        {
            cacheIndex = 0;
            base.VisibleControls.Clear();

            if (CheckFlag(InvalidationFlag.Size | InvalidationFlag.Layout))
            {
                Size size = ClientBounds.Size;
                cellOffsets = new GridOffsets(Definition, size);
                if (TransitionState != null)
                {
                    targetOffsets = new GridOffsets(TransitionState, size);
                }
                if (state != null)
                {
                    stateOffsets = new GridOffsets(state, size);
                }
            }
            if (state != null || transitionState != null)
            {
                LayoutWithTransition();
            }
            else
            {
                LayoutWithoutTransition();
            }

        }

        private void LayoutWithTransition()
        {
            CellState anchorState = new CellState();
            CellState currentState = new CellState();

            for (int index = 0; index < this.Definition.Cells.Count; index++)
            {
                Control ctrl = GetControl(index);
                if (ctrl != null && ctrl.Visible)
                {
                    GridCell referenceCell = Definition.Cells[index];
                    Rectangle refBounds = GetCellBounds(cellOffsets, referenceCell);
                    anchorState.Initialize(referenceCell, refBounds);

                    if (state != null && state != definition)
                    {
                        GridCell stateCell = state.Cells[index];
                        Rectangle stateBounds = GetCellBounds(stateOffsets, stateCell);
                        currentState.Initialize(stateCell, stateBounds);

                        anchorState.Interpolate(currentState, level, MaxLevel);
                    }
                    if (transitionState != null)
                    {
                        GridCell targetCell = transitionState.Cells[index];
                        Rectangle targetBounds = GetCellBounds(targetOffsets, targetCell);
                        currentState.Initialize(targetCell, targetBounds);

                        anchorState.Interpolate(currentState, transitionLevel, MaxLevel);
                    }

                    if (anchorState.Visible)
                    {
                        GridContainer container = GetGridContainer();
                        anchorState.ApplyToControl(container);
                        container.Control = ctrl;
                        base.VisibleControls.AddFirst(container);
                        Layouter.Layout(ctrl, container.Size);

                    }
                }
            }
        }

        private void LayoutWithoutTransition()
        {
            int index = 0;
            foreach (GridCell c in Cells)
            {
                Control ctrl = GetControl(index);
                if (ctrl != null)
                {
                    base.VisibleControls.AddLast(ctrl);
                    Rectangle r = GetCellBounds(cellOffsets, c);
                    ctrl.Location = Point.Empty;
                    Layouter.Layout(ctrl, r.Size);
                    ctrl.Location = new Point(r.X + ctrl.X, r.Y + ctrl.Y);
                    ctrl.Opacity = Cells[index].Alpha;
                }
                index++;
            }
        }

        private GridContainer GetGridContainer()
        {
            if (containerCache == null) containerCache = new List<GridContainer>();
            if (cacheIndex >= containerCache.Count)
            {
                GridContainer c = new GridContainer();
                containerCache.Add(c);
                cacheIndex++;
                return c;
            }
            else
            {
                GridContainer c = containerCache[cacheIndex++];
                c.Transformation = null;
                c.Opacity = 255;
                return c;
            }
        }

        private Control GetControl(int index)
        {
            Control c;
            if (Controls.TryGetValue(index, out c)) return c;
            return null;
        }

        private Rectangle GetCellBounds(GridOffsets cellSizes, GridCell c)
        {
            Rectangle r = new Rectangle();
            int row = c.Row;
            int col = c.Col;
            int x0 = col > 0 ? cellSizes.ColOffsets[col - 1] : 0;
            int y0 = row > 0 ? cellSizes.RowOffsets[row - 1] : 0;

            r.X = x0;
            r.Y = y0;
            col += c.ColSpan - 1;
            row += c.RowSpan - 1;

            r.Width = cellSizes.ColOffsets[col] - x0;
            r.Height = cellSizes.RowOffsets[row] - y0;
            return r;
        }

        public void AddControl(int cellIndex, Control control)
        {
            Controls.Add(cellIndex, control);
        }

        #endregion
    }
}
